﻿using System.Web;
using Castle.Core;
using FlexiCommerce.Persistence;
using FlexiCommerce.Plugin;
using FlexiCommerce.Security;
using FlexiCommerce.Web;

namespace FlexiCommerce.Engine
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Linq;
    using System.Text;

    using Castle.Core.Resource;
    using Castle.MicroKernel;
    using Castle.Windsor;
    using Castle.Windsor.Configuration.Interpreters;
    using Castle.Windsor.Installer;

    using FlexiCommerce.Configuration;

    public class Engine : IEngine
    {

        #region Fields

        private IWindsorContainer container;

        #endregion Fields

        #region Constructors
        public Engine(EventBroker broker)
			: this()
		{
			AddComponentInstance(broker);
		}

        public Engine(System.Configuration.Configuration config, string sectionGroup, EventBroker broker)
		{
			if(string.IsNullOrEmpty(sectionGroup)) throw new ArgumentException("Must be non-empty and match a section group in the configuration file.", "sectionGroup");

			container = new WindsorContainer();
			
			RegisterConfigurationSections(config, sectionGroup);
			HostSection hostConfig = AddComponentInstance<HostSection>(config.GetSection(sectionGroup + "/host") as HostSection);
			EngineSection engineConfig = AddComponentInstance<EngineSection>(config.GetSection(sectionGroup + "/engine") as EngineSection);
			DatabaseSection dbConfig = AddComponentInstance<DatabaseSection>(config.GetSection(sectionGroup + "/database") as DatabaseSection);
			InitializeEnvironment(hostConfig, engineConfig);
			IResource resource = DetermineResource(engineConfig, config.GetSection("castle") != null);
			ProcessResource(resource);
            InstallComponents();
			AddComponentInstance(broker);
		}
        protected void RegisterConfigurationSections(System.Configuration.Configuration config, string sectionGroup)
        {
            object nhConfiguration = config.GetSection("hibernate-configuration");
            if (nhConfiguration != null)
                container.Kernel.AddComponentInstance("hibernate-configuration", nhConfiguration);
            SectionGroup n2group = config.GetSectionGroup(sectionGroup) as SectionGroup;
            if (n2group != null)
            {
                foreach (ConfigurationSection section in n2group.Sections)
                {
                    container.Kernel.AddComponentInstance(section.SectionInformation.Name, section);
                }
            }
        }
        public Engine()
        {
            container = new WindsorContainer();

            HostSection hostConfig = AddComponentInstance<HostSection>(ConfigurationManager.GetSection("n2/host") as HostSection);
            EngineSection engineConfig = AddComponentInstance<EngineSection>(ConfigurationManager.GetSection("n2/engine") as EngineSection);
            DatabaseSection dbConfig = AddComponentInstance<DatabaseSection>(ConfigurationManager.GetSection("n2/database") as DatabaseSection);

            InitializeEnvironment(hostConfig, engineConfig);
            IResource resource = DetermineResource(engineConfig, ConfigurationManager.GetSection("castle") != null);
            ProcessResource(resource);
            InstallComponents();
        }

        #endregion Constructors

        #region Properties

        public IWindsorContainer Container
        {
            get { return container; }
            set { container = value; }
        }
        public ISecurityManager SecurityManager
        {
            get { return container.Resolve<ISecurityManager>(); }
        }

        public IWebContext RequestContext
        {
            get { return container.Resolve<IWebContext>(); }
        }

        public IPersister Persister
        {
            get { return container.Resolve<IPersister>(); }
        }

        #endregion Properties

        #region Methods

        public void AddComponent(string key, Type classType)
        {
            Container.AddComponent(key, classType);
        }

        public void AddComponent(string key, Type serviceType, Type classType)
        {
            Container.AddComponent(key, serviceType, classType);
        }

        public void AddComponentInstance(string key, Type serviceType, object instance)
        {
            Container.Kernel.AddComponentInstance(key, serviceType, instance);
        }

        public T AddComponentInstance<T>(T instance)
            where T : class
        {
            if (instance != null)
            {
                AddComponentInstance(typeof(T).Name, typeof(T), instance);
            }
            return instance as T;
        }

        public void AddFacility(string key, object facility)
        {
            if (facility is IFacility)
                Container.AddFacility(key, facility as IFacility);
            else
                throw new ArgumentException("Only classes implementing Castle.MicroKernel.IFacilty are supported.");
        }

        public void Initialize()
        {
            AddComponentInstance<IEngine>(this);

            IPluginBootstrapper invoker = Container.Resolve<IPluginBootstrapper>();
            invoker.InitializePlugins(this, invoker.GetPluginDefinitions());

            StartComponents(container.Kernel);
            container.Kernel.ComponentCreated += Kernel_ComponentCreated;
        }
        void Kernel_ComponentCreated(ComponentModel model, object instance)
        {
            if (instance is IStartable)
                return;

            IAutoStart startable = instance as IAutoStart;
            if (startable != null)
            {
                startable.Start();
            }
        }
        private static void StartComponents(IKernel kernel)
        {
            INamingSubSystem naming = kernel.GetSubSystem(SubSystemConstants.NamingKey) as INamingSubSystem;
            foreach (GraphNode node in kernel.GraphNodes)
            {
                ComponentModel model = node as ComponentModel;
                if (model != null)
                {
                    if (typeof(IStartable).IsAssignableFrom(model.Implementation) || typeof(IAutoStart).IsAssignableFrom(model.Implementation))
                    {
                        IHandler h = naming.GetHandler(model.Name);
                        if (h.CurrentState == HandlerState.Valid)
                        {
                            object component = kernel[model.Name];
                            if (component is IStartable)
                                (component as IStartable).Start();
                            else if (component is IAutoStart)
                                (component as IAutoStart).Start();
                        }
                    }
                }
            }
            kernel.AddFacility<Castle.Facilities.Startable.StartableFacility>();
        }

        public void Release(object instance)
        {
            Container.Release(instance);
        }

        public T Resolve<T>()
            where T : class
        {
            return Container.Resolve<T>();
        }

        public object Resolve(Type serviceType)
        {
            return Container.Resolve(serviceType);
        }

        public object Resolve(string key)
        {
            return Container.Resolve(key);
        }

        protected IResource DetermineResource(EngineSection engineConfig, bool hasCastleSection)
        {
            if (engineConfig != null)
            {
                if (!string.IsNullOrEmpty(engineConfig.CastleSection))
                    return new ConfigResource(engineConfig.CastleSection);
                else
                    return new AssemblyResource(engineConfig.CastleConfiguration);
            }
            else if (hasCastleSection)
            {
                return new ConfigResource();
            }
            else
            {
                throw new ConfigurationErrorsException("Couldn't find a suitable configuration section for FlexiCommerce. Either add an flexicommerce/engine or a castle configuartion section to web.config. Note that this section may have changed from previous versions. Please verify that the configuartion is properly updated.");
            }
        }

        protected void InstallComponents()
        {

                DefaultComponentInstaller installer = new DefaultComponentInstaller();
                installer.SetUp(container, container.Kernel.ConfigurationStore);

            
        }

        protected void ProcessResource(IResource resource)
        {
            XmlInterpreter interpreter = new XmlInterpreter(resource);
            interpreter.ProcessResource(resource, container.Kernel.ConfigurationStore);
        }

        private void InitializeEnvironment(HostSection hostConfig, EngineSection engineConfig)
        {
            if (hostConfig != null && engineConfig != null)
            {

                RegisterConfiguredComponents(engineConfig);

                if (!hostConfig.IsWeb)
                    container.Kernel.AddComponentInstance("n2.webContext.notWeb", typeof(IWebContext), new ThreadContext());

                //TODO: uncomment for url parser.
                //if (hostConfig.Web.Urls.EnableCaching)
                //    container.Register(Component.For<IUrlParser>().Named("n2.web.cachingUrlParser").ImplementedBy<CachingUrlParserDecorator>());
                //container.Register(DetermineUrlParser(hostConfig));
            }
        }

        private void RegisterConfiguredComponents(EngineSection engineConfig)
        {
            foreach (ComponentElement component in engineConfig.Components)
            {
                Type implementation = Type.GetType(component.Implementation);
                Type service = Type.GetType(component.Service) ?? implementation;
                if (service != null)
                    container.AddComponent(service.FullName, service, implementation);
            }
        }

        public FlexiCommerce.Localization.ILocalizationManager LocalizationManager
        {
            get { return Resolve<FlexiCommerce.Localization.ILocalizationManager>(); }
        }

        #endregion Methods


        #region IEngine Members


        public IUrlParser UrlParser
        {
            get { return Resolve<IUrlParser>(); }
        }

        #endregion
    }
}